package com.xuecheng.auth.service;

import com.alibaba.fastjson.JSON;
import com.xuecheng.framework.client.XcServiceList;
import com.xuecheng.framework.domain.ucenter.ext.AuthToken;
import com.xuecheng.framework.domain.ucenter.response.AuthCode;
import com.xuecheng.framework.exception.ExceptionCast;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 认证服务接口
 */
@Service
public class AuthService {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${auth.tokenValiditySeconds}")
    private Long ttl;

    /**
     * 登录认证
     *
     * @param username     用户名
     * @param password     密码
     * @param clientId     客户端id
     * @param clientSecret 客户端密码
     * @return 封装了令牌信息
     */
    public AuthToken login(String username, String password, String clientId, String clientSecret) {
        //1. 申请令牌
        AuthToken authToken = this.applyToken(username, password, clientId, clientSecret);
        //2. 保存令牌到redis
        String jti = authToken.getJti();
        String authToken_Json = JSON.toJSONString(authToken);
        boolean flag = this.saveToken2Redis(jti, authToken_Json, ttl);
        if (!flag) {
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
        }
        return authToken;
    }


    /**
     * 保存令牌信息到redis中
     *
     * @param jti            用户短令牌, 用于在redis中查找对应的jwt令牌
     * @param authToken_Json json字符串格式令牌
     * @param ttl            超时时间
     * @return
     */
    private boolean saveToken2Redis(String jti, String authToken_Json, long ttl) {
        //redis键名称
        String key = "user_token:" + jti;
        //保存到令牌到redis
        try {
            stringRedisTemplate.boundValueOps(key).set(authToken_Json, ttl, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据用户短令牌从redis中查找jwt令牌(access_token)
     * @param jti_token
     * @return
     */
    public AuthToken getUserToken(String jti_token) {
        String key = "user_token:" + jti_token;
        String authToken_json = stringRedisTemplate.opsForValue().get(key);
        //如果查不到
        if (StringUtils.isEmpty(authToken_json)) {
            return null;
        }
        return JSON.parseObject(authToken_json, AuthToken.class);
    }

    /**
     * 删除redis中的令牌
     * @param jti_token
     */
    public void delToken(String jti_token){
        String key = "user_token:" + jti_token;
        try {
            stringRedisTemplate.delete(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 申请令牌
     *
     * @param username     用户名
     * @param password     密码
     * @param clientId     客户端id
     * @param clientSecret 客户端密码
     * @return 封装了令牌信息
     */
    public AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
        //1. 从Eureka中获取地址
        ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
        URI uri = serviceInstance.getUri();
        String authUrl = uri + "/auth/oauth/token";

        //请求的内容分两部分
        //2.1 请求头信息，包括了http basic认证信息
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String httpBasic = this.httpBasic(clientId, clientSecret);
        headers.add("Authorization", httpBasic);
        //2.2 请求体信息, 包括：grant_type、username、passowrd
        MultiValueMap<String, String> body = new LinkedMultiValueMap<String, String>();
        body.add("grant_type", "password");
        body.add("username", username);
        body.add("password", password);

        HttpEntity<MultiValueMap<String, String>> multiValueMapHttpEntity = new HttpEntity<MultiValueMap<String, String>>(body, headers);
        //指定 restTemplate当遇到400或401响应时候也不要抛出异常，也要正常返回值
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                //当响应的值为400或401时候也要正常响应，不要抛出异常
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
                    super.handleError(response);
                }
            }
        });

        //远程调用令牌申请
        ResponseEntity<Map> exchange = restTemplate.exchange(authUrl, HttpMethod.POST, multiValueMapHttpEntity, Map.class);
        //获得令牌信息
        Map exchangeBody = exchange.getBody();

        if(exchangeBody ==null) {
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }
        //如果用户输错账号或密码
            //取出令牌中的错误信息
        String error_description = (String) exchangeBody.get("error_description");
        if (!StringUtils.isEmpty(error_description)) {
            if (error_description.equals("坏的凭证") || error_description.contains("UserDetailsService returned null")) {
                //抛出账号或密码错误异常
                ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
            }
        }
        //如果是账号密码之外的错误,则走下面这个if
        if (StringUtils.isEmpty(exchangeBody.get("access_token"))
                || StringUtils.isEmpty(exchangeBody.get("refresh_token"))
                || StringUtils.isEmpty(exchangeBody.get("jti"))) {
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }
        //***获取令牌成功
        //3. 封装结果
        AuthToken authToken = new AuthToken();
        //3.1 封装jwt令牌
        String access_token = (String) exchangeBody.get("access_token");
        authToken.setAccess_token(access_token);
        //3.2 封装刷新令牌
        String refresh_token = (String) exchangeBody.get("refresh_token");
        authToken.setRefresh_token(refresh_token);
        //3.3 用户访问令牌(短令牌)
        String jti = (String) exchangeBody.get("jti");
        authToken.setJti(jti);
        return authToken;
    }

    /**
     * Base64编码
     * 将客户端id和客户端密码进行编码
     *
     * @param clientId     客户端id
     * @param clientSecret 客户端密码
     * @return 编码后的字符串结果
     */
    private String httpBasic(String clientId, String clientSecret) {
        //将客户端id和客户端密码拼接，按  “客户端id:客户端密码”
        String string = clientId + ":" + clientSecret;
        //进行base64编码
        byte[] encode = Base64Utils.encode(string.getBytes());
        return "Basic " + new String(encode);
    }


}
